package com.xyw.code.platform.webapps.log;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xyw.code.common.core.constant.IsDelEnum;
import com.xyw.code.common.core.entity.vo.Result;
import com.xyw.code.common.core.util.UserContextHolder;
import com.xyw.code.platform.common.entity.enums.LogConstant;
import com.xyw.code.platform.webapps.util.LogUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.security.SecurityUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * Created by xuyiwei on 2019/8/12.
 * 系统日志切面
 * ①切面注解得到请求数据 -> ②发布监听事件 -> ③异步监听日志入库
 */
@Slf4j
@Aspect
@Component
public class SysLogAspect {

    private ThreadLocal<com.xyw.code.platform.common.entity.po.SysLog > threadLocal = new ThreadLocal<>();
    /**
     * 事件发布是由ApplicationContext对象管控的，我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布
     **/
    @Autowired
    private ApplicationContext applicationContext;

    /***
     * 定义controller切入点拦截规则，拦截SysLog注解的方法
     */
    @Pointcut("@annotation(com.xyw.code.platform.webapps.log.SysLog)")
    public void sysLogAspect() {

    }

    /***
     * 拦截控制层的操作日志
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Before(value = "sysLogAspect()")
    public void recordLog(JoinPoint joinPoint) throws Throwable {
        com.xyw.code.platform.common.entity.po.SysLog  loginLog = new com.xyw.code.platform.common.entity.po.SysLog();
        threadLocal.set(loginLog);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        loginLog.setRequestIp(ServletUtil.getClientIP(request));
        loginLog.setDescription(LogUtil.getControllerMethodDescription(joinPoint));
        loginLog.setActionMethod(joinPoint.getSignature().getName());
        loginLog.setActionUrl(URLUtil.getPath(request.getRequestURI()));
        loginLog.setParams(detailArgs(joinPoint.getArgs()));
        loginLog.setUa(request.getHeader("user-agent"));
        loginLog.setClassPath(joinPoint.getTarget().getClass().getName());
        loginLog.setRequestMethod(request.getMethod());
        loginLog.setStartTime(new Date(System.currentTimeMillis()));
        loginLog.setDelFlag(IsDelEnum.USE.getKey());
        //判断是不是登录日志
        String requestUrl = URLUtil.getPath(request.getRequestURI());
        if("/login".equals(requestUrl)){
            loginLog.setType(LogConstant.LogTypeEnum.LOGIN_TYPE.getType());
        }else{
            if(UserContextHolder.getInstance().getContext()!=null){
                loginLog.setUserName(UserContextHolder.getInstance().getContext().getUsername());
            }
            loginLog.setType(LogConstant.LogTypeEnum.OP_TYPE.getType());
        }
    }


    /**
     * 返回通知
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable {
        com.xyw.code.platform.common.entity.po.SysLog sysLog = threadLocal.get();
        threadLocal.remove();
        sysLog = getConsumingTime(sysLog);
        // 处理完请求，返回内容
        Result r = Convert.convert(Result.class, ret);
        if (!"200".equals(r.getCode())){
            //如果不是正常返回 则记录异常状态
            sysLog.setType(LogConstant.LogTypeEnum.ERROR_TYPE.getType());
            sysLog.setExDetail(r.getMsg());
        }
        // 发布事件
        applicationContext.publishEvent(new SysLogEvent(sysLog));
    }

    /**
     * 异常通知
     * @param e
     */
    @AfterThrowing(pointcut = "sysLogAspect()",throwing = "e")
    public void doAfterThrowable(JoinPoint joinPoint, Throwable e) throws Throwable{
        com.xyw.code.platform.common.entity.po.SysLog sysLog = threadLocal.get();
        threadLocal.remove();
        sysLog = getConsumingTime(sysLog);
        // 异常
        sysLog.setType(LogConstant.LogTypeEnum.ERROR_TYPE.getType());
        // 异常对象
        sysLog.setExDetail(LogUtil.getStackTrace(e));
        // 异常信息
        sysLog.setExDesc(e.getMessage());
        // 发布事件
        applicationContext.publishEvent(new SysLogEvent(sysLog));
    }

    /**
     * 得到消耗的时间
     * @param sysLog
     * @return
     */
    private com.xyw.code.platform.common.entity.po.SysLog getConsumingTime(com.xyw.code.platform.common.entity.po.SysLog sysLog){
        Date startTime = sysLog.getStartTime();
        Long nowTime = System.currentTimeMillis();
        sysLog.setFinishTime(new Date(nowTime));
        sysLog.setConsumingTime(nowTime-startTime.getTime());
        return sysLog;
    }

    /**
     * 处理数据库保存的参数 隐藏敏感信息
     * @param args
     * @return
     */
    private String detailArgs(Object[] args) {
        if(args.length>0){
            String argsJson = JSONObject.toJSONString(args[0]);
            //如果是修改密码和登录则移除密码参数
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String requestUrl = URLUtil.getPath(request.getRequestURI());
            if("/login".equals(requestUrl)){
                JSONObject jsonObject = JSON.parseObject(argsJson);
                jsonObject.remove("password");
                return jsonObject.toJSONString();
            }else{
                return argsJson;
            }
        }else{
            return null;
        }
    }
}
